/*
 * ServicesApplication.java
 *
 * Created on April 22, 2007, 12:28 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.xeerkat.agent.apps;

import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.restlet.Application;
import org.restlet.Client;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.representation.StringRepresentation;
import org.restlet.routing.Router;
import org.xeerkat.agent.AgentContext;
import org.xeerkat.agent.AgentMonitor;
import org.xeerkat.agent.ResponseListener;
import org.xeerkat.agent.services.ServiceMetadata;
import org.xeerkat.agent.util.XSDDate;
import org.xeerkat.xmpp.ID;

/**
 *
 * @author alex
 */
@ServiceMetadata(id="http://www.xeerkat.org/Services/Monitor/2007/1/0",vendor="xeerkat.org",copyright="2008 xeerkat.org")
public class MonitorApplication extends Application
{
   
   static URI NAMESPACE = URI.create("http://www.xeerkat.org/Vocabulary/Services/2007/1/0");
   static class Stat {
      ID id;
      Date lastCheck;
      Date lastResponse;
      Stat(ID id)
      {
         this.id = id;
         this.lastCheck = new Date();
         this.lastResponse = null;
      }
   }
   AgentContext agentContext;
   Thread checkThread;
   AtomicBoolean checkRunning;
   Map<ID,Stat> stats;
   long checkInterval;
   
   public MonitorApplication(Context context)
   {
      agentContext = (AgentContext)context.getAttributes().get(AgentContext.AGENT_CONTEXT_ATTR);
      String svalue = context.getParameters().getFirstValue("status.interval");
      checkInterval = svalue==null ? 60*1000*5 : Long.parseLong(svalue);
      stats = new TreeMap<ID,Stat>();
   }
   
   public Restlet createRoot() {
      Router router = new Router();
      Restlet servicesApp = new Restlet(getContext()) {
         public void handle(Request request, Response response) {
            Form form = request.getResourceRef().getQueryAsForm();
            String xmpp = form.getFirstValue("xmpp");
            AgentMonitor monitor = agentContext.getAgentMonitor();
            response.setStatus(Status.SUCCESS_OK);
            StringBuilder builder = new StringBuilder();
            builder.append("<agents for='"+agentContext.getIdentity()+"' xmlns='"+NAMESPACE+"'>\n");
            for (ID id : monitor.getAgents()) {
               if (xmpp!=null && !id.toXMPPId().startsWith(xmpp)) {
                  continue;
               }
               Stat stat = stats.get(id);
               String lastCheck = stat==null ? null : XSDDate.toString(stat.lastCheck);
               String lastResponse = stat!=null && stat.lastResponse!=null ? XSDDate.toString(stat.lastResponse) : null;
               builder.append("<agent id='"+id+"'");
               if (lastCheck!=null) {
                  builder.append(" checked='");
                  builder.append(lastCheck);
                  builder.append("'");
               }
               if (lastResponse!=null) {
                  builder.append(" response='");
                  builder.append(lastResponse);
                  builder.append("'");
               }
               builder.append("/>\n");
            }
            builder.append("</agents>\n");
            response.setEntity(new StringRepresentation(builder.toString(),MediaType.APPLICATION_XML));
         }
      };
      router.attach("/",servicesApp);
      router.attach("",servicesApp);
      return router;
   }
   
   public void start() 
      throws Exception
   {
      super.start();
      
      checkRunning = new AtomicBoolean(true);
      checkThread = new Thread(new Runnable() {
         Client client = getContext().getClientDispatcher();
         public void run() {

            while (checkRunning.get()) {
               
               boolean reconnect = false;
               AgentMonitor monitor = agentContext.getAgentMonitor();

               // Remove agents that went offline
               List<ID> toRemove = new ArrayList<ID>();
               for (ID id : stats.keySet()) {
                  if (!monitor.getAgents().contains(id)) {
                     toRemove.add(id);
                  }
               }
               for (ID id : toRemove) {
                  stats.remove(id);
               }
               
               for (ID id : monitor.getAgents()) {
                  
                  if (stats.get(id)==null) {
                     stats.put(id,new Stat(id));
                  }
                  final Stat stat = stats.get(id);
                  // We're using the async protocol
                  // This get's us xeerkat://{self}/{id}/ping/echo in the correct format
                  Reference uri = agentContext.createRequestReference(AgentContext.ASYNC_PROTOCOL,id.toString(),"/status");
                  getLogger().info("Status check on "+id);

                  stat.lastCheck = new Date();
                  // Make a GET request on the rest service for echo
                  Request request = new Request(Method.GET,uri);
                  Response response = client.handle(request);

                  // Success means we could send the request and not much more
                  if (!response.getStatus().isSuccess()) {
                     getLogger().warning("Cannot send status check request to "+id);
                  } else {
                     Form headers = (Form)response.getAttributes().get("org.restlet.http.headers");
                     String responseId = headers.getValues("x-response-id");
                     agentContext.registerResponseListener(responseId,new ResponseListener() {
                        public void onResponse(String id,Response response) {
                           stat.lastResponse = new Date();
                        }
                     });
                     
                  }
               }
               try {
                  Thread.currentThread().sleep(checkInterval);
               } catch (InterruptedException ex) {

               }
            }
         }
      });
      checkThread.start();
   }
   
   public void stop() 
      throws Exception
   {
      super.stop();
      checkRunning.set(false);
      synchronized (checkThread) {
         checkThread.notify();
      }
      checkThread.join();
   }
}
